package com.jiuqi.bi.metagraph.server.graph.query;

import com.jiuqi.bi.metagraph.server.graph.GraphException;
import com.jiuqi.bi.metagraph.server.graph.bean.CorrelationTableBean;
import com.jiuqi.bi.metagraph.server.graph.bean.EntityBean;
import com.jiuqi.bi.metagraph.server.graph.bean.RelationBean;
import com.jiuqi.bi.metagraph.server.graph.enums.Direction;
import com.jiuqi.bi.metagraph.server.graph.enums.RelationType;
import com.jiuqi.bi.metagraph.server.graph.manager.MetaGraphManager;
import com.jiuqi.bi.metagraph.server.graph.processor.AbstractEntityProcessor;
import com.jiuqi.bi.metagraph.server.graph.processor.EntityProcessorManager;
import com.jiuqi.bi.util.StringUtils;

import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * 用于图谱实体的查询，以及实体之间的关系查询
 * @author lianghaojun
 *
 */
public class GraphQueryEngine {

    private MetaGraphManager graphManager = new MetaGraphManager();

    /**
     * 查询实体对象
     * @param entityGuid 实体guid
     * @return 实体对象
     * @throws GraphException
     */
    public EntityBean getEntity(String entityGuid) throws GraphException{
        return graphManager.getEntity(entityGuid);
    }
    
    /**
     * 查询实体对象
     * @param queryOption 查询选项
     * @return 实体的列表
     * @throws GraphException
     */
    @SuppressWarnings("unused")
	public List<EntityBean> getEntities(QueryOption queryOption) throws GraphException{
        // 从数据库查询
        return graphManager.getEntities(queryOption);
    }
    
    /**
     * 查询实体对象
     * @param queryOption 查询选项
     * @param pageInfo 分页信息
     * @return 实体的列表
     * @throws GraphException
     */
    @SuppressWarnings("unused")
	public List<EntityBean> getEntities(QueryOption queryOption, PageInfo pageInfo) throws GraphException{
        // 从数据库查询
        return graphManager.getEntities(queryOption,pageInfo);
    }
    
    
    
    /**
     * 获取尾部实体
     * <p>
     * relationTypes为空，则查询所有类型
     * @param headEntityGuid 首部实体guid
     * @param relationTypes 关系类型
     * @return 实体的列表
     * @throws GraphException
     */
    public List<EntityBean> getTailEntities(String headEntityGuid, RelationType... relationTypes) throws GraphException{
        return graphManager.getTailEntities(headEntityGuid,relationTypes);
    }
    
    /**
     * 获取首部实体
     * <p>
     * relationTypes为空，则查询所有类型
     * @param tailEntityGuid 尾部实体guid
     * @param relationTypes 关系类型
     * @return 实体的列表
     * @throws GraphException
     */
    public List<EntityBean> getHeadEntities(String tailEntityGuid, RelationType... relationTypes) throws GraphException{
        return graphManager.getHeadEntities(tailEntityGuid,relationTypes);
    }
    
    /**
     * 获取尾部关系
     * <p>
     * relationTypes为空，则查询所有类型
     * @param headEntityGuid 首部实体guid
     * @param relationTypes 关系类型
     * @return 关系的列表
     * @throws GraphException
     */
    public List<RelationBean> getTailRelations(String headEntityGuid, RelationType... relationTypes) throws GraphException{
        return graphManager.getTailRelations(headEntityGuid,relationTypes);
    }
    
    /**
     * 获取首部关系
     * <p>
     * relationTypes为空，则查询所有类型
     * @param tailEntityGuid 尾部实体guid
     * @param relationTypes 关系类型
     * @return 关系的列表
     * @throws GraphException
     */
    public List<RelationBean> getHeadRelations(String tailEntityGuid, RelationType... relationTypes) throws GraphException{
        return graphManager.getHeadRelations(tailEntityGuid,relationTypes);
    }

    /**
     * 获取包含的实体列表
     * @return
     */
    public List<EntityBean> getContainedEntities(String entityGuid) throws GraphException {
        return graphManager.getContainedEntities(entityGuid);

    }


    /**
     * 获取有关联的所有级次的实体
     * entityTypes为空，则查询所有类型
     * @param entityGuid 实体guid
     * @param direction 方向
     * @param entityTypes 实体类型
     * @return  实体的列表
     * @throws GraphException
     */
    public  List<EntityBean> getRelatedEntities(String entityGuid, Direction direction, String... entityTypes) throws GraphException{
        List<EntityBean> result = new ArrayList<EntityBean>();
        List<EntityBean> headEntities = getHeadEntities(entityGuid);
        doGetRelatedEntities(headEntities,result,direction, Arrays.asList(entityTypes));
        return result;
    }

    /**
     * 元数据图谱相关性分析
     * @param entityGuid        根节点实体guid
     * @param isBloodRelation   是否为血缘关系
     * @param types             过滤类型
     * @param filterString      过滤名称
     * @return
     * @throws GraphException
     */
    public  List<CorrelationTableBean> getRelatedEntities(String entityGuid, boolean isBloodRelation,String types,String filterString) throws GraphException{

        //条件过滤内部类（对实体类型、实体名称进行过滤）
        class Filter{
            public Boolean filterMethod(EntityProcessorManager processorManager,EntityBean entityBean,String types,String filterString){

                String entityTypeTitle = processorManager.getTypeTitle(entityBean.getType());
                Boolean typeNonJudge = types.indexOf("\""+entityTypeTitle+"\"") == -1;
                Boolean titleNonJudge = StringUtils.isNotEmpty(filterString)
                                        &&
                                        !(entityBean.getTitle().toLowerCase().indexOf(filterString.trim()) != -1 || entityBean.getTitle().toUpperCase().indexOf(filterString.trim()) != -1);

                if (typeNonJudge || titleNonJudge){
                    return false;
                }else {
                    return true;
                }
            }
        }
        //如果实体类型为空或长度为0时，直接返回null
        if (StringUtils.isEmpty(types))
            return null;

        //图谱实体处理器的管理类
        EntityProcessorManager processorManager = new EntityProcessorManager();
        //过渡集合
        IdentityHashMap<EntityBean,List<EntityBean>> result = new IdentityHashMap<>();
      
        //查询初始根节点
        EntityBean rootNode = getEntity(entityGuid);

        //【生成与root节点具有  直接关系  的相关性路径】【最小长度路径】
        //查询血缘或影响关系
        List<EntityBean> subEntityBeanList = isBloodRelation ?  getTailEntities(entityGuid) : getHeadEntities(entityGuid);
     

        subEntityBeanList.stream()
                .forEach(entityBean -> {
                    if (entityBean.isIndependent() && new Filter().filterMethod(processorManager, entityBean, types, filterString)){

                        //由各个实体组成，记录遍历路径
                        List<EntityBean> pathEntityList = new ArrayList<>();
                        pathEntityList.add(rootNode);
                        pathEntityList.add(entityBean);
                        //根据查询类型生成两种关系路径
                        //血缘关系    X--->X--->X--->A
                        //影响关系    A--->X--->X--->X
                        if (isBloodRelation) {
                            Collections.reverse(pathEntityList);
                        }
                        //使用EntityBean作为key是为了方便前端处理
                        result.put(entityBean,pathEntityList);
                    }
                });


        //【生成与root节点具有  间接关系  的相关性路径】
        //非递归的深度优先遍历
        //栈① 存放待处理的节点
        Deque<EntityBean> stackFirst = new ArrayDeque<>();
        //栈② 记录公有祖先节点路径用于生成完整路径
        Deque<EntityBean> stackSecond = new ArrayDeque<>();
        //判断集合  用于判断各个根节点是否为失效节点，若为失效节点则进行删除
        IdentityHashMap<EntityBean,List<EntityBean>> judgeMap = new IdentityHashMap<>();


        //初始根节点 入栈①
        stackFirst.addLast(rootNode);


        //当栈①中待处理的节点不为空时执行（当栈①为空时，说明待处理节点为空，遍历结束）
        while (stackFirst.size()>0){
            //栈① 顶部元素出栈
            EntityBean node = stackFirst.removeLast();

            //判断是否为根节点
            String nodeGuid = node.getGuid();
            //血缘查询不查询包含关系    影响查询需要查询全部关系
            List<EntityBean> subEntityBeans = isBloodRelation ? getTailEntities(nodeGuid) : getHeadEntities(nodeGuid);

            //判断这些子节点的方向性，过滤逆向节点
            List<EntityBean> transition = new ArrayList<>();
            subEntityBeans.stream()
                    .filter(checkBean->{
                        return !stackSecond.contains(checkBean);
                    })
                    .forEach(bean ->{
                        if(bean.isIndependent()){
                            transition.add(bean);
                        }
                    });
            subEntityBeans = transition;

            //若为根节点，并且该根节点的子节点不全为逆向节点
            if (subEntityBeans.size() > 0){
                //将该根节点加入栈②
                stackSecond.addLast(node);
                //存储正向子节点的集合
                List<EntityBean> nodeList =new ArrayList<>();
                //将该节点的正向子节点都加入 栈①
                subEntityBeans.stream()
                        .forEach(entityBean->{
                                //非逆向子节点入栈①(确保不形成闭环)、节点集合
                                stackFirst.addLast(entityBean);
                                nodeList.add(entityBean);
                        });
                //将该节点和其子节点加入判断集合中
                judgeMap.put(node,nodeList);

            //当判断为叶子节点 或者 某一根节点的子节点全为逆向节点，则该路径已经走到尽头了，可以生成路径了
            }else{
                //当栈②中元素为空时，说明多叉树顶端根节点是叶子节点
                if (stackSecond.size()==0) {
                    break;
                }

                //当满足过滤条件时才生成路径
                if (new Filter().filterMethod(processorManager, node, types, filterString)){

                    //由各个实体组成，记录遍历路径
                    List<EntityBean> pathEntityBeanList = new ArrayList<>();
                    //获取共有的遍历路径
                    Iterator<EntityBean> iterator = stackSecond.iterator();
                    while (iterator.hasNext()){
                        pathEntityBeanList.add(iterator.next());
                    }
                    pathEntityBeanList.add(node);
                    //根据查询类型生成两种关系路径
                    //血缘关系    X--->X--->X--->A
                    //影响关系    A--->X--->X--->X
                    if (isBloodRelation) {
                        Collections.reverse(pathEntityBeanList);
                    }
                    //将此条遍历路径加入 路径集合
                    //具有直接关系的相关性路径在一开始已经生成，但此时仍有可能生成，需要过滤直接关系
                    if (pathEntityBeanList.size()>2) {
                        //使用EntityBean作为key是为了方便前端处理
                        result.put(node,pathEntityBeanList);
                    }
                }


                //一条路径准备生成后（无论是否生成成功），判断集合中情况需要更新
                List<EntityBean> entityBeanList = judgeMap.get(stackSecond.peekLast());
                //删除已经生成了路径的节点元素,judgeMap是自动更新的（因为是一个对象）
                entityBeanList.remove(node);


                //当一条路径生成后，可能存在删除该条路径共有节点的情况，需要进行判断
                //存在连续删除情况（类似于祖玛弹球，当删除一个节点A时，删除节点A的这个行为，使另一个节点B的删除条件得到了满足，节点B也应该连带着删除）
                //因此删除栈②中元素的操作，需要多次判断，当满足条件时应多次执行
                int stackNum = stackSecond.size();
                while (stackNum > 0){
                    //如果stackNum 不等于 stackSecond.size()的值  说明上一次删除条件不成立，如果上一次没有删除节点，那么连续删除的情况不会存在，可以结束循环
                    if (stackNum != stackSecond.size()) {
                        break;
                    }

                    //查看栈②顶部元素，并以该元素作为key去判断集合中查询其value
                    EntityBean judgmentalDeletionNode = stackSecond.peekLast();
                    List<EntityBean> judgmentalDeletionNodeChildList = judgeMap.get(judgmentalDeletionNode);

                    //当判断集合中某一key对应的value为空时，说明该key对应的根节点不再被其子节点需要用于生成路径(因为key对应根节点的所有子节点都生成了对应路径)
                    //则栈② 栈顶元素应删除（该元素对应着判断集合中的key）
                    if (judgmentalDeletionNodeChildList.size() == 0){

                        /**
                         * 此时情况说明：
                         * 与初始根节点（多叉树顶部的节点） 具有【直接关系】的节点集合
                         *                                                   都已经与 初始根节点 生成了路径（路径集合1）
                         **************************************************************************************************
                         * 与初始根节点（多叉树顶部的节点） 具有【间接关系】并且在某一条查询路径上的节点集合
                         *                                                  只有集合中的叶子节点与 初始根节点 生成了路径（路径集合2）
                         *                                                  此时还需其余非叶子节点与 初始根节点 生成路径（路径集合3）
                         ***************************************************************************************************
                         *  某条查询路径所有关系路径 = 路径集合1中一条（已存在） + 路径集合2（已存在） + 路径集合3（待生成）
                         */
                        //如果栈顶元素符合过滤要求，则在删除栈② 栈顶元素之前，需要基于该元素生成路径集合3中的对应路径
                        if (new Filter().filterMethod(processorManager, judgmentalDeletionNode, types, filterString)){
                            //由各个实体组成，记录遍历路径
                            List<EntityBean> pathEntityBeanList1 =new ArrayList<>();
                            //获取共有的遍历路径
                            Iterator<EntityBean> iterator1 = stackSecond.iterator();
                            while (iterator1.hasNext()){
                                pathEntityBeanList1.add(iterator1.next());
                            }
                            //根据查询类型生成两种关系路径
                            //血缘关系    X--->X--->X--->A
                            //影响关系    A--->X--->X--->X
                            if (isBloodRelation) {
                                Collections.reverse(pathEntityBeanList1);
                            }
                            //将此条遍历路径加入 路径集合
                            //具有直接关系的相关性路径在一开始已经生成，但此时仍有可能生成，需要过滤直接关系
                            EntityBean tagNode = stackSecond.peekLast();
                            if (pathEntityBeanList1.size()>2) {
                                //使用EntityBean作为key是为了方便前端处理
                                result.put(tagNode,pathEntityBeanList1);
                            }
                        }

                        //删除并获取栈②顶部元素
                        EntityBean deletedEntityBean = stackSecond.removeLast();

                        //删除此节点，judgeMap的情况需要动态更新(起前后衔接作用)
                        //当栈②  大小为0 时，所有根节点（是包含子节点的节点，包括顶部的根节点）对应hashmap中的value都为空，不再进行后续操作，跳出循环（stackNum > 0）
                        if (stackSecond.size() == 0) {
                            break;
                        }
                        //因为栈②顶部元素已经删除，故需要动态更新判断集合judgeMap内的情况
                        EntityBean updateNode =  stackSecond.peekLast();
                        List<EntityBean> updateNodeChildList = judgeMap.get(updateNode);
                        updateNodeChildList.remove(deletedEntityBean);
                    }
                    //结束循环的因子
                    stackNum --;
                }
            }
        }

        //进行对象转换，方便前端使用
        List<CorrelationTableBean> correlationTableBeans=new ArrayList<>();

        //循环遍历集合
        Set<Map.Entry<EntityBean, List<EntityBean>>> entries = result.entrySet();
        //循环遍历集合
        for (Map.Entry<EntityBean, List<EntityBean>> entry:entries) {
            EntityBean key = entry.getKey();
            List<EntityBean> value = entry.getValue();
            //将前端需要的信息处理成一个个具体的对象
            String path = getPath(key.getType(),key.isIndependent() ? key.getGuid() : key.getOwnerGuid());

            // 处理标识
            String name = StringUtils.isNotEmpty(key.getName()) ? key.getName() : "暂无";
            CorrelationTableBean correlationTableBean=new CorrelationTableBean(key.getGuid(), key.getTitle(), name,key.getType(),processorManager.getTypeTitle(key.getType()), path, value,value.size());

            //以集合的形式传给前端。
            correlationTableBeans.add(correlationTableBean);
        }



        List<CorrelationTableBean> firstSortedCorrelationTableBeans = new ArrayList<>();

        //之后按照类型标题进行分组     eg:  “信息资源”-->ArrayList（size=8）  “接入点”-->ArrayList（size=9）
        Map<String, List<CorrelationTableBean>> firstCollect = correlationTableBeans.stream().collect(Collectors.groupingBy(CorrelationTableBean::getType));
        firstCollect.entrySet().stream()
                .forEach(i->{
                        //当类型分组完成后，最后对资源标题进行分组
                        i.getValue().stream()
                                .collect(Collectors.groupingBy(CorrelationTableBean::getTitle)).entrySet()
                                                                                         .forEach(it->{
                                                                                             it.getValue()
                                                                                                     .forEach(ite->{
                                                                                                         firstSortedCorrelationTableBeans.add(ite);
                                                                                                     });
                                                                                         });
                });


        List<CorrelationTableBean> finalSortedCorrelationTableBeans = new ArrayList<>();

        //首先根据 路径层级 排序
        firstSortedCorrelationTableBeans.stream()
                .sorted(new Comparator<CorrelationTableBean>() {
                    @Override
                    public int compare(CorrelationTableBean o1, CorrelationTableBean o2) {
                        return o1.getPathLength() - o2.getPathLength();
                    }
                }).forEach(i->{
                    finalSortedCorrelationTableBeans.add(i);
                });

        return finalSortedCorrelationTableBeans;

    }

    /**
     * 获取实体对象的路径信息
     * @param type
     * @param entityGuid
     * @return
     */
    public String getPath(String type, String entityGuid) {
        AbstractEntityProcessor processor = new EntityProcessorManager().getProcessor(type);
        if(processor == null){
            return null;
        }
        try {
            return processor.getPath(type,entityGuid);
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 递归查询有关联的实体
     * @param entityBeans
     * @param result
     * @param direction
     * @param entityTypes
     * @throws GraphException
     */
    private void doGetRelatedEntities(List<EntityBean> entityBeans, List<EntityBean> result, Direction direction, List<String> entityTypes) throws GraphException {
        for(EntityBean entityBean: entityBeans){
            if(entityTypes.size() > 0  && !entityTypes.contains(entityBean.getType())){//不符合类型的实体跳过
                continue;
            }
            result.add(entityBean);
            String guid = entityBean.getGuid();
            List<EntityBean> headEntities1 = Direction.IN == direction ? getHeadEntities(guid) : getTailEntities(guid);
            doGetRelatedEntities(headEntities1,result,direction, entityTypes);
        }
    }

}
